//Copyright (c) 2007  lin yong
//  All rights reserved.
//this is free software without any WARRANTIE

#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/signal.h>
#include <stdio.h>
//#include <avr/timer.h>
//#include <avr/crc16.h>
#include <avr/pgmspace.h>
//#include <wdt.h>
#include <emb48.h>
#define	DELAY_TIME_T0	255-8

#define	START_ADDRESS 	0X0000
#define	VERSION	0xb0
//change start point here

volatile unsigned char tick=0;
//time interrupt
//internal clock is used at 1MHz speed*the system default
SIGNAL  (SIG_OVERFLOW0)
{	TCNT0=DELAY_TIME_T0;
	tick++;
	if (tick>0xf0) {tick=0xf0;}
}
//isp transmittion 
unsigned char isp_transmit(unsigned char send_byte)
{
	SPDR = send_byte;
  
  while (!(SPSR & (1 << SPIF)));
  return SPDR;
}
//tick provider
void delay(unsigned int time)
{	
	tick=0;
	for (;time>0;)
	{
		if (tick>0) {tick=0;time--;}
	}
}
//used the eerom to register the process,
void EEPROM_write(unsigned int uiAddress, unsigned char ucData)
{
/* Wait for completion of previous write */
while(EECR & (1<<EEWE))
;
/* Set up address and data registers */
EEAR = uiAddress;
EEDR = ucData;
/* Write logical one to EEMWE */
EECR |= (1<<EEMWE);
/* Start eeprom write by setting EEWE */
EECR |= (1<<EEWE);
}

#define A	(1<<PC0)
#define B	(1<<PC1)
#define C	(1<<PC2)
#define D	(1<<PC3)
#define E	(1<<PC4)
#define F	(1<<PC5)
#define G	(1<<PC6)
//translate the machine code the readable code
unsigned char translate(unsigned char value)
{
	unsigned char i=0;
	switch (value)
		{
			case 0x00: i=(~(A|B|C|D|E|F));
					 break;
			case 0x01: i=(~(B|C));
					 break;
			case 0x02: i=(~(A|B|D|E|G));
					 break;
			case 0x03: i=(~(A|B|C|D|G));
					 break;
			case 0x04: i=(~(B|C|F|G));
					 break;
			case 0x05: i=(~(A|C|D|F|G));
					 break;
			case 0x06: i=(~(A|C|D|E|F|G));
					 break;
			case 0x07: i=(~(A|B|C));
					 break;
			case 0x08: i=(~(A|B|C|D|E|F|G));
					 break;
			case 0x09: i=(~(A|B|C|D|F|G));
					 break;
			case 0x0a: i=(~(A|B|C|E|F|G));
					 break;
			case 0x0b: i=(~(C|D|E|F|G));
					 break;
			case 0x0c: i=(~(A|D|E|F));
					 break;
			case 0x0d: i=(~(A|B|C|D|E|G));
					 break;
			case 0x0e: i=(~(A|D|E|F|G));
					 break;
			case 0x0f: i=(~(A|E|F|G));
					 break;
		}
	return (i);
}
//use the led as the interface
//important: the fuse "jtagen" in m16 should be disabled
void display(unsigned char value)
{
	PORTC=translate((value>>4)&0x0f);
	PORTD=translate(value&0x0f);
}
//recording fault
void alarm(void)
{
	
	EEPROM_write(0,0xcc);
	for(;;);
}
//initiate the reset pin of the target chip
//in most occasions the traget is not ready or synchronized, 
//so reset the target is very important.
void target_initiate(void)
{
	
	PORTB&=~(1<<PB7);
	delay(2);
	PORTB|= (1<<PB3);
	delay(2);
	PORTB&=~(1<<PB3);
	delay(10);
	
}
//firstly the source and traget systems should be synchronized
//secondly the programe enable command makes the target runing into 
//embbeding process
void program_enable(void)
{
	unsigned char check=0;
	unsigned char count = 4;
	for(;check!=0x53;)
	{
	target_initiate();
	//delay(30);

	while ((count--)&&(check!=0x53))
	{
		delay(2);
		isp_transmit(0xAC);
		isp_transmit(0x53);
		check = isp_transmit(0);
		isp_transmit(0);
	}
	}
    //if (check!=0x53) {alarm();}//
	
}
//timer0 as the tick provider
void intiate_timer0(void)
{
	TIMSK|=1<<TOIE0;
	TCCR0=0b00000101;//101;
	TCNT0=DELAY_TIME_T0;
	sei();
}
//the host chip works as master, the target works automaticly as slave
static __inline__ void SPI_MasterInit(void)
{
	/* Set MOSI and SCK output, all others input */
	DDRB |= (1<<PB7)|(1<<PB5);//|(1<<PB2);
	DDRB |= (1<<PB3);
	/* Enable SPI, Master, set clock rate fck/16 */
	SPCR |= (1<<SPE)|(1<<MSTR)|(1<<SPR0);//|(1<<SPR1);//
	//SPSR |= (1<<SPI2X);
}
//read one id
unsigned char read_id(unsigned char locate)
{
	isp_transmit(0x30);
	isp_transmit(0);
	isp_transmit(locate);
	return(isp_transmit(0));
}
//check all 3ids 
//if one id is wrong, the downloading should be stopped
void checking_ID(void)
{
	unsigned char id_store;
	//delay(25);
	//program_enable();
	id_store=read_id(0);
	if (id_store!=0x1e) {display(0xe1);alarm();}
	id_store=read_id(1);
	if (id_store!=0x92) {display(0xe2);alarm();}
	id_store=read_id(2);
	if (id_store!=0x05) {display(0xe3);alarm();}
	//PORTA&=~(1<<PA0);
	//EEPROM_write(0,id_store);
	//for(;;);
	delay(5);
	
}
//test the previous work is done
unsigned char busy(void)
{
	isp_transmit(0xf0);
	isp_transmit(0);
	isp_transmit(0);
	return(isp_transmit(0)&1);
}
void write_onepage(unsigned int target_addr)
{
	isp_transmit(0x4C);
	isp_transmit((unsigned char)(target_addr >> 9));
	isp_transmit((unsigned char)(target_addr >> 1));
	isp_transmit(0);
}
//flash is written page by page
void write_flash(void)
{
	unsigned int origin_addr=START_ADDRESS,target_addr=0;
	unsigned char page,counter,data;
	for (page=0;taregt_addr<=FLASH_TAR;page++)
	{
		for (counter=0;counter<SPM_TAR;counter++)
		{
			data=pgm_read_byte_near(origin_addr);
			isp_transmit(0x40 | (unsigned char)(((target_addr & 1) << 3)));
			isp_transmit((unsigned char)(target_addr >> 9));
			isp_transmit((unsigned char)(target_addr >> 1));
			isp_transmit(data);	
			origin_addr++;
			target_addr++;
		}
		//write one page here
		target_addr--;
		write_onepage(target_addr);
		delay(2);
		//change the next page and wait
		while(busy());
		target_addr++;
	}
	
}
//test the content is correct or not
unsigned char checking_flash(void)
{
	unsigned int origin_addr=START_ADDRESS,target_addr=0;
	unsigned char error_mark=0,data,temp;
	for (target_addr=0;target_addr<=FLASH_TAR;)//FLASH48
	{
		data=pgm_read_byte_near(origin_addr);
		isp_transmit(0x20 |  (unsigned char)(((target_addr & 1) << 3)));
		isp_transmit( (unsigned char)(target_addr >> 9));
		isp_transmit( (unsigned char)(target_addr >> 1));
		temp=isp_transmit(0);
		if (data!=temp)
		{
		error_mark=1;
		}
		target_addr++;
		origin_addr++;
	}
	return (error_mark);
}
//chip erasing, see the fuse setting in target chip handbook
void chip_erase(void)
{
		isp_transmit(0xAC);
		isp_transmit(0x80);
		isp_transmit(0);
		isp_transmit(0);
		delay(10);
}
//the following process is all the same but different 
//for different target chip.
//see the target chip manual
unsigned char read_fuse_bit(void)
{
	isp_transmit(0x50);
	isp_transmit(0);
	isp_transmit(0);
	return(isp_transmit(0));
}
unsigned char read_fuse_high_bit(void)
{
	isp_transmit(0x58);
	isp_transmit(0x08);
	isp_transmit(0);
	return(isp_transmit(0));
}
unsigned char read_fuse_ext_bit(void)
{
	isp_transmit(0x50);
	isp_transmit(0x08);
	isp_transmit(0);
	return(isp_transmit(0)|0b11111110);
}
void write_fuse_bit(void)
{
	isp_transmit(0xac);
	isp_transmit(0xa0);
	isp_transmit(0);
	isp_transmit(BIT_LOW);
	while(busy());
}
void write_fuse_high_bit(void)
{
	isp_transmit(0xac);
	isp_transmit(0xa8);
	isp_transmit(0);
	isp_transmit(BIT_H);
	while(busy());
}
void write_fuse_ext_bit(void)
{
	isp_transmit(0xac);
	isp_transmit(0xa4);
	isp_transmit(0);
	isp_transmit(BIT_EXT);
	while(busy());
}
void write_lock(void)
{
	isp_transmit(0xac);
	isp_transmit(0xe0);
	isp_transmit(0);
	isp_transmit(BIT_LOCK);
	while(busy());

}
unsigned char read_lock_bit(void)
{
	isp_transmit(0x58);
	isp_transmit(0);
	isp_transmit(0);
	return(isp_transmit(0)|0b11111100);
}
void show_license(void)
{
	
	display(VERSION);
	
}
void intiate_display(void)
{
	DDRD=0xff;
	DDRC=0xff;
	PORTA=0xff;
	show_license();
	
}
void intiate_connect(void)
{
	DDRB=1<<PB1;
	PORTB&=~(1<<PB1);
}
int main (void)
{
	unsigned char repeat=0;

	intiate_display();
	
	intiate_timer0();
	SPI_MasterInit();
	intiate_connect();
	
	//*********************
	display(0x01);
	program_enable();
	checking_ID();
	
	display(0x02);
	chip_erase();
	display(0x03);
	
	write_flash();
	display(0x04);
	
	repeat=checking_flash();
	display(0x05);
	
	if (repeat==1) {display(0xe4);alarm();}
	
	write_fuse_bit();
	write_fuse_high_bit();
	write_fuse_ext_bit();
	
	if ((BIT_LOW!=read_fuse_bit())||(BIT_H!=read_fuse_high_bit())||(BIT_EXT!=read_fuse_ext_bit())) 
	{display(0xe5);alarm();};
	
	write_lock();
	
	if (BIT_LOCK!=read_lock_bit()) {display(0xe6);alarm();};
	display(0x88);
	return (0);
}